package org.game.manager;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sojoys.artifact.manager.MessageManager;
import com.sojoys.artifact.tools.ToolError;
import com.xiaoleilu.hutool.util.MapUtil;
import com.xiaoleilu.hutool.util.RandomUtil;
import org.cfg.EquipmentCfg;
import org.cfg.EquipmentGrowthCfg;
import org.cfg.EquipmentRandomCfg;
import org.cfg.EquipmentSuitCfg;
import org.cfg.cache.DiscreteDataCfgCache;
import org.cfg.cache.EquipmentCfgCache;
import org.cfg.cache.EquipmentGrowthCfgCache;
import org.cfg.cache.EquipmentRandomCfgCache;
import org.cfg.cache.EquipmentSuitCfgCache;
import org.constant.*;
import org.error.GameErrorCode;
import org.game.constant.AttrConstant;
import org.game.constant.EquipmentConstant;
import org.game.db.bean.Hero;
import org.game.db.bean.Item;
import org.game.db.bean.Player;
import org.game.db.cache.HeroCache;
import org.game.db.proxy.bean.HeroProxy;
import org.game.db.vo.BackpackVO;
import org.game.db.vo.EquipmentVO;
import org.game.log.builder.LogDsp;
import org.game.log.dsp.GoodsDsp;
import org.game.module.itembo.ItemBO;
import org.game.msgBuilder.EquipmentMsgBuilder;
import org.game.msgBuilder.ItemMsgBuilder;
import org.game.plugin.event.GameEvent;
import org.game.plugin.event.GameEventPlugin;
import org.game.protobuf.s2c.S2CItemMsg;
import org.game.protobuf.s2c.S2CShareMsg;
import org.game.util.ProtoUnit;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 装备管理器
 * Created by fxf on 2017/9/26.
 */
@SuppressWarnings("unchecked")
public class EquipmentManager {

    private EquipmentManager() {
    }

    private static EquipmentManager instance = new EquipmentManager();

    public static EquipmentManager me() {
        return instance;
    }

    /**
     * 计算灵装属性
     */
    public Map<Integer, Integer> calcEquipAttr(Hero hero) {
        Map<Integer, Integer> attrMap = Maps.newHashMap();
        if (hero.getHeroProxy().getEquipment().size() == 0)
            return attrMap;
        hero.getHeroProxy().getEquipment().values().forEach(equipment -> {
            EquipmentCfg cfg = EquipmentCfgCache.me().getById(equipment.getCid());
            int level = equipment.getLevel();

            //计算基础属性
            cfg.getBaseAttribute().forEach((k, v) -> {
                int attrType = (int) k;
                int attrValue = (int) v;
                int growth = (int) cfg.getGrowthAttribute().get(attrType);
                attrValue += growth * (level - 1);
                int otherValue = attrMap.computeIfAbsent(attrType, key -> 0);
                attrMap.put(attrType, otherValue + attrValue);
            });

            //计算特殊属性
            List<List<Integer>> specialAttrList = equipment.getSpecialAttrList();
            specialAttrList.forEach(attrInfo -> {
                int type = attrInfo.get(EquipmentVO.SPECIAL_ATTR_INDEX_TYPE);
                int value = attrInfo.get(EquipmentVO.SPECIAL_ATTR_INDEX_VALUE);
                int otherValue = attrMap.computeIfAbsent(type, key -> 0);
                attrMap.put(type, otherValue + value);
            });
        });
        return attrMap;
    }

    /**
     * 初始化灵装
     * 随机灵装的特殊属性
     */
    public void initEquipment(Item item, EquipmentCfg equipmentCfg) {
        EquipmentVO equipmentVO = (EquipmentVO) item;
        List<Integer> attrTypeList = getSpecialAttrIdList(equipmentCfg);
        List<List<Integer>> attrList = createSpecialAttrMap(equipmentCfg,attrTypeList);
        equipmentVO.setSpecialAttrList(attrList);
    }

    /**
     * 装备灵装
     *
     * @param player    玩家
     * @param hero      英雄
     * @param equipment 灵装
     * @param position  装备位置
     */
    public byte[] equip(Player player, Hero hero, EquipmentVO equipment, int position) {

        EquipmentVO oldEquipment = hero.getHeroProxy().getEquipment().get(position);

        checkHeroCost(hero, equipment, oldEquipment);

        if (oldEquipment != null) {
            oldEquipment.takeOff();
            oldEquipment.update();
        }

        equipment.equip(hero.getId(), position);
        equipment.update();

        hero.getHeroProxy().getEquipment().put(position, equipment);
        // 重新计算英雄属性
 		HeroManager.me().calAttr(hero);
        HeroManager.sendHeroInfo2Client(player,hero, S2CShareMsg.ChangeType.UPDATE);
        return ProtoUnit.toByte(EquipmentMsgBuilder.getEquipMsg(equipment, oldEquipment));
    }

    public byte[] takeOff(Player player, Hero hero, int position) {
        EquipmentVO equipment = hero.getHeroProxy().getEquipment().remove(position);
        equipment.takeOff();
        equipment.update();
        // 重新计算英雄属性
  		HeroManager.me().calAttr(hero);
        HeroManager.sendHeroInfo2Client(player,hero, S2CShareMsg.ChangeType.UPDATE);
        sendEquipmentInfo(player, equipment);

        return ProtoUnit.toByte(EquipmentMsgBuilder.getTakeOffMsg(equipment, hero));
    }


    /**
     * 灵装洗炼
     *
     * @param sourceEquipment 被洗炼的灵装
     * @param oldAttrIndex    将被替换的属性位置
     * @param costEquipment   被消耗的灵装
     */
    public byte[] changeSpecialAttr(Player player, EquipmentVO sourceEquipment, int oldAttrIndex, EquipmentVO costEquipment) {
        List<Integer> newAttr = getNewAttr(costEquipment);
        int attrIndexInCostEquipment =  newAttr.remove(newAttr.size() - 1);
        sourceEquipment.setTempSpecialAttr(oldAttrIndex, newAttr);
        sourceEquipment.update();
        Map<Integer, Map<Integer,Integer>> priceMap = (Map<Integer, Map<Integer, Integer>>) DiscreteDataCfgCache.me().getData(DiscreteDataID.EQUIP_ATTR_CHANGE,DiscreteDataKey.EQUIPMENT_CHANGE_ATTR_PRICE);

        int star = sourceEquipment.getCfg().getStar();

        // 洗练灵装日志
        LogDsp log = LogDsp.newBuilder(GoodsDsp.EQUIPMENT_CHANGE_ATTR).targetTag(sourceEquipment.getCid());
        LogDsp cloneLog = log.clone();
        // 洗练灵装消耗道具
        ItemManager.costGoodsByCid(player,priceMap.get(star),log,S2CItemMsg.ItemList.newBuilder());
        // 洗练灵装吃掉得灵装
        ItemManager.costGoodsByDBId(player,MapUtil.of(costEquipment.getId(),1),cloneLog, S2CItemMsg.ItemList.newBuilder());

        return ProtoUnit.toByte(EquipmentMsgBuilder.getChangeSpecialAttrMsg(oldAttrIndex, attrIndexInCostEquipment));
    }

    /**
     * 强化灵装
     * <p>
     * 强化时客户端必须判断金钱是否足够
     *
     * @param sourceEquipment   被强化的灵装
     * @param costEquipmentList 扣除的灵装列表
     */
    public byte[] upgrade(Player player, EquipmentVO sourceEquipment, List<EquipmentVO> costEquipmentList) {

        EquipmentGrowthCfg growthCfg = EquipmentGrowthCfgCache.me().getByStar(sourceEquipment.getCfg().getStar());
        int[] expList = growthCfg.getNeedExp();

        List<EquipmentVO> actualCostList = Lists.newArrayList();
        int costGold = 0;
        Map<Long,Integer> costInfo = Maps.newHashMap();
        int costRatio = DiscreteDataCfgCache.me().getIntData(DiscreteDataID.EQUIP_UPGRADE,DiscreteDataKey.EQUIPMENT_UPDATE_COST_RATIO);

        StringBuilder costStr = new StringBuilder();
        //逐件消耗灵装
        for (EquipmentVO costEquipment : costEquipmentList) {
            //判断强化的灵装是否达到上限
            if (sourceEquipment.getLevel() >= expList.length)
                break;
            //判断金钱是否足够
            int exp = getEquipmentExp(costEquipment);
//            if (ItemManager.getGoldNum(player) < exp + costGold)
            if (player.getPlayerProxy().getTokenNum(ItemConstantId.GOLD) < exp + costGold)
                break;
            costInfo.put(costEquipment.getId(),1);
            addEquipmentExp(sourceEquipment, exp, expList);
            actualCostList.add(costEquipment);
            costGold += exp * costRatio / 10000;
            costStr.append(costEquipment.getCid()).append(",");
        }
        LogDsp log = LogDsp.newBuilder(GoodsDsp.EQUIPMENT_UPGRADE).targetTag(sourceEquipment.getCid());
        LogDsp cloneLog = log.clone();
        
        ItemManager.costGoodsById(player, costInfo.keySet().toArray(new Long[costInfo.size()]), costInfo.values().toArray(new Integer[costInfo.size()]), log, S2CItemMsg.ItemList.newBuilder());
        
        
        ItemManager.costGoodsByCid(player, MapUtil.of(ItemConstantId.GOLD, costGold), cloneLog, S2CItemMsg.ItemList.newBuilder());
        
        sourceEquipment.update();

        sendEquipmentInfo(player, sourceEquipment);

        Hero hero = null;
        if (sourceEquipment.getPosition() != 0) {
            hero = HeroCache.me().getById(sourceEquipment.getHeroId());
            HeroManager.sendHeroInfo2Client(player,hero, S2CShareMsg.ChangeType.UPDATE);
        }
        
        Map<String, Object> in = Maps.newHashMap();
        in.put(EventConditionKey.CONDITION_TYPE, EventConditionType.EQUIP_MAX_LVL);
		in.put(EventConditionKey.EQUIPMENT_CID, sourceEquipment.getCid());
		in.put(EventConditionKey.NOW_LEVEL, sourceEquipment.getLevel());
		GameEvent event = new GameEvent(player, EventType.EQUIPMENT, in);
		GameEventPlugin.syncSubmit(event);

        return ProtoUnit.toByte(EquipmentMsgBuilder.getUpgradeMsg());
    }



    public byte[] replaceSpecialAttr(Player player, EquipmentVO equipment, boolean replace) {
        Hero hero = null;
        if (replace){
            equipment.changeAttr();
            if (equipment.getHeroId() != 0){
                hero = HeroCache.me().getById(equipment.getHeroId());
                HeroManager.sendHeroInfo2Client(player,hero, S2CShareMsg.ChangeType.UPDATE);
            }
        }
        equipment.removeTempAttr();
        equipment.update();
        sendEquipmentInfo(player,equipment);
        return ProtoUnit.toByte(EquipmentMsgBuilder.getReplaceSpecialAttrMsg());
    }

    //~~~~~~~~~~~~~~~~~~~~~~~~~~~ private method ~~~~~~~~~~~~~~~~~~~~~~~~~~~//

    /**
     * 灵装增加经验
     *
     * @param equipmentVO 灵装
     * @param gainExp     获得的经验
     * @param expList     灵装升级经验列表
     */
    private void addEquipmentExp(EquipmentVO equipmentVO, int gainExp, int[] expList) {

        while (gainExp > 0
                && equipmentVO.getLevel() < expList.length
                && equipmentVO.getLevel() <= equipmentVO.getCfg().getMaxLevel()) {
            int upgradeExp = expList[equipmentVO.getLevel() - 1] - equipmentVO.getExp();
            if (gainExp >= upgradeExp) {
                equipmentVO.setLevel(equipmentVO.getLevel() + 1);
                equipmentVO.setExp(0);
                gainExp -= upgradeExp;
            } else {
                equipmentVO.addExp(gainExp);
                gainExp = 0;
            }
        }
    }

    /**
     * 计算被消耗的灵装提供的经验
     */
    private int getEquipmentExp(EquipmentVO equipment) {
        EquipmentGrowthCfg growthCfg = EquipmentGrowthCfgCache.me().getByStar(equipment.getCfg().getStar());

        int growthExp = 0;
        if (equipment.getLevel() > 1) {

            for (int i = 0; i < growthCfg.getNeedExp().length; i++) {
                if (i >= equipment.getLevel() - 1)
                    break;
                growthExp += (int) growthCfg.getNeedExp()[i];
            }
        }

        return equipment.getCfg().getExp() + growthExp;
    }

    /**
     * 根据权重随机获得新的Attribute（从被消耗的灵装的特殊属性里筛选）
     */
    private List<Integer> getNewAttr(EquipmentVO costEquipment) {
        int totalWeight = 0;
        List<EquipmentRandomCfg> cfgList = Lists.newArrayList();
        for (List<Integer> attrInfoList : costEquipment.getSpecialAttrList()) {
            EquipmentRandomCfg cfg = EquipmentRandomCfgCache.me().getById(attrInfoList.get(EquipmentVO.SPECIAL_ATTR_INDEX_CFG_ID));
            cfgList.add(cfg);
            totalWeight += cfg.getWeight();
        }

        int randomWeight = RandomUtil.randomInt(totalWeight);
        int weightSum = 0;

        List<Integer> newAttr = Lists.newArrayList();
        for (int i = 0; i < cfgList.size(); i++) {
            EquipmentRandomCfg cfg = cfgList.get(i);
            weightSum += cfg.getWeight();
            if (randomWeight <= weightSum) {
            	newAttr.addAll(costEquipment.getSpecialAttrList().get(i));
                newAttr.add(i);
                break;
            }
        }
        return newAttr;
    }

    /**
     * 获取特殊属性id
     * 随机获取，不重复
     */
    private List<Integer> getSpecialAttrIdList(EquipmentCfg equipmentCfg) {
        int specialAttrNum = EquipmentConstant.SPECIAL_ATTR_NUM[equipmentCfg.getStar()];
        List<Integer> selectedAttr = Lists.newArrayList();
        int maxWeight = equipmentCfg.getSpecialAttrTotalWeight();

        for (int i = 0; i < specialAttrNum; i++) {
            int randomWeight = RandomUtil.randomInt(maxWeight);
            int weightSum = 0;
            for (Object e : equipmentCfg.getSpecialAttribute().entrySet()) {
                Map.Entry<Integer, Integer> entry = (Map.Entry<Integer, Integer>) e;
                int id = entry.getKey();
                int weight = entry.getValue();

                if (selectedAttr.contains(id))
                    continue;

                weightSum += weight;
                if (weightSum >= randomWeight) {
                    selectedAttr.add(id);
                    maxWeight -= weight;
                    break;
                }
            }
        }
        return selectedAttr;
    }

    /**
     * 生成特殊属性
     * 1.根据权重获取特殊属性
     * 2.在特殊属性取值范围内随机一个字作为属性值
     */
    public List<List<Integer>> createSpecialAttrMap(EquipmentCfg equipmentCfg,List<Integer> typeList) {
        List<List<Integer>> result = Lists.newArrayList();
        List<List<Integer>> attrLevelRange = equipmentCfg.getSpecialAttrLevelRange();
        for (int i = 0; i < typeList.size(); i++) {
            int type = typeList.get(i);
            int maxWeight = EquipmentRandomCfgCache.me().getMaxWeightByType(type, attrLevelRange.get(i));
            int randomWeight = RandomUtil.randomInt(maxWeight);
            List<EquipmentRandomCfg> cfgList = EquipmentRandomCfgCache.me().getBySuperTypeLevelRange(type, attrLevelRange.get(i));
            int weightSum = 0;
            for (EquipmentRandomCfg equipmentRandomCfg : cfgList) {
                weightSum += equipmentRandomCfg.getWeight();
                if (weightSum >= randomWeight) {
                    int attrValue = RandomUtil.randomInt(equipmentRandomCfg.getAttribute()[0], equipmentRandomCfg.getAttribute()[1]);
                    Integer[] attrArray = new Integer[]{equipmentRandomCfg.getAttrType(), attrValue, equipmentRandomCfg.getId()};
                    result.add(Arrays.asList(attrArray));
                    break;
                }
            }
        }
        return result;
    }


    /**
     * 检查hero负载
     * @param hero 英雄
     * @param newEquipment 新装备
     * @param oldEquipment 旧装备
     */
    private void checkHeroCost(Hero hero, EquipmentVO newEquipment, EquipmentVO oldEquipment) {
    	HeroProxy heroExt = hero.getHeroProxy();
        int totalCost = heroExt.getAttr().get(AttrConstant.ATTR_COST);
        int afterCost = newEquipment.getCfg().getCost();
        for (EquipmentVO equipmentVO : heroExt.getEquipment().values()) {
            if (equipmentVO == oldEquipment) {
                continue;
            }
            afterCost += equipmentVO.getCfg().getCost();
        }
        ToolError.isAndTrue(GameErrorCode.COST_OVERLOAD, "cost过载，无法装备", afterCost > totalCost);
    }

    /**
     * 发送灵装信息到前台
     * @param player 玩家
     * @param equipment 灵装
     */
    private void sendEquipmentInfo(Player player, EquipmentVO equipment) {
        S2CItemMsg.ItemList.Builder builder = S2CItemMsg.ItemList.newBuilder();
        ItemMsgBuilder.packageItemInfo(S2CShareMsg.ChangeType.UPDATE, builder,equipment);
        player.getPlayerProxy().sendMsg(MessageManager.me().create(ItemBO.getItems, ProtoUnit.toByte(builder.build())));
    }
    
    /**
     * 检查拥有套装
     * @param coverId
     */
    public boolean checkHaveCover(Player player,int coverId){
    	EquipmentSuitCfg suitCfg = EquipmentSuitCfgCache.me().getById(coverId);
    	if (suitCfg != null) {
    		List<EquipmentCfg> equipCfgs = EquipmentCfgCache.me().getBySuit(suitCfg.getId());
    		BackpackVO backpackVO = player.getPlayerProxy().getBackpackByType(BagType.EQUIPMENT);
    		boolean bool = true;
    		for (EquipmentCfg equipmentCfg : equipCfgs) {
    			bool &= (backpackVO.countByCfg(equipmentCfg) > 0);
			}
    		return bool;
		}else {
			return false;
		}
    }
}
